import { AbstractControl, FormGroup, AsyncValidatorFn, ValidationErrors } from '@angular/forms'
import { Observable, of } from 'rxjs'
import { map } from 'rxjs/operators'

import { BusinessService } from '@shared/services/business.service'
import { EBizLevel } from '@shared/enums'
import { IBizUniqueness } from '@shared/interfaces/biz'
import { TerminalStoreService } from 'app/routes/terminal/new/terminal-store.service'

export function ValidateUserPassword(control: AbstractControl) {
  const value = control.value
  const lowerCaseReg = /[a-z]+/
  const upperCaseReg = /[A-Z]+/
  const digitReg = /[0-9]+/
  const letterReg = /[_\s,!"#$%&'()*+,-./ :;<=>?@[\]^`{|}~\\]+/

  let matchCount = 0
  if (lowerCaseReg.test(value)) {
    matchCount++
  }
  if (upperCaseReg.test(value)) {
    matchCount++
  }
  if (digitReg.test(value)) {
    matchCount++
  }
  if (letterReg.test(value)) {
    matchCount++
  }
  if (matchCount < 3) {
    return { passwordInvalid: true }
  }
  return null
}

export function UsernameConflictAsyncValidator(
  service: BusinessService,
  fromGroups: FormGroup[],
  field: string,
): AsyncValidatorFn {
  return (ctrl: AbstractControl): Observable<ValidationErrors | null> => {
    if (!ctrl || !ctrl.value) {
      return of(null)
    }

    for (const form of fromGroups) {
      const fieldCtrl = form.get(field)
      if (ctrl !== fieldCtrl && ctrl.value === fieldCtrl.value) {
        const err = { usernameConflicting: true }
        return of(err)
      }
    }

    const params: IBizUniqueness = {
      level: EBizLevel.User,
      username: ctrl.value,
    }
    return service.checkUniqueness(params).pipe(
      map(result => {
        if (!result) {
          return { usernameConflicting: true }
        }

        return null
      }),
    )
  }
}

export function TermCodeConflictAsyncValidator(
  bizService: BusinessService,
  fromGroups: FormGroup[],
  field: string,
  intStoreCode: string,
): AsyncValidatorFn {
  return (ctrl: AbstractControl): Observable<ValidationErrors | null> => {
    if (!ctrl || !ctrl.value) {
      return of(null)
    }

    for (const form of fromGroups) {
      const fieldCtrl = form.get(field)
      if (ctrl !== fieldCtrl && ctrl.value === fieldCtrl.value) {
        const err = { termCodeConflicting: true }
        return of(err)
      }
    }
    const params: IBizUniqueness = {
      level: EBizLevel.Terminal,
      termCode: ctrl.value,
      intStoreCode,
    }
    return bizService.checkUniqueness(params).pipe(
      map(result => {
        if (!result) {
          return { isNumTaken: true }
        }
        return null
      }),
    )
  }
}

export function NumConflictAsyncValidator(
  service: BusinessService,
  insCode: string,
  level: EBizLevel,
  intStoreCode?: string,
): AsyncValidatorFn {
  return (ctrl: AbstractControl): Observable<ValidationErrors | null> => {
    if (!ctrl || !ctrl.value) {
      return of(null)
    }

    const params: IBizUniqueness = {
      level,
      insCode,
    }

    switch (level) {
      case EBizLevel.Institution:
        params.insCode = ctrl.value
        break
      case EBizLevel.Merchant:
        params.merNum = ctrl.value
        break
      case EBizLevel.Store:
        params.storeNum = ctrl.value
        break
      case EBizLevel.Terminal:
        delete params.insCode
        params.termCode = ctrl.value
        params.intStoreCode = intStoreCode
        break
      default:
        return of(null)
    }

    return service.checkUniqueness(params).pipe(
      map(isOk => {
        // return null if it's valid after async validation
        return !isOk ? { isNumTaken: true } : null
      }),
    )
  }
}

export function currencyConvertUniqueValidator(
  validateForms: FormGroup[],
  selfField: string,
  oppositeField: string
) {
  return (ctrl: AbstractControl): Observable<ValidationErrors | null> => {
    if (!ctrl || !ctrl.value) {
      return of(null)
    }
    const ctrlFormGroup = ctrl.parent
    let err =  null
    validateForms.forEach((form) => {
      if (!ctrlFormGroup || !form) return
      if (ctrlFormGroup === form) return
      if (
        form.get(selfField)
        && form.get(oppositeField)
        && form.get(selfField).value === ctrl.value
        && form.get(oppositeField).value === ctrlFormGroup.get(oppositeField).value
      ) {
        err = { existCurrConvert: true }
      }
    })
    if (!err) {
      validateForms.forEach(form => {
        if (form.get(selfField).value) {
          form.get(selfField).setErrors(null)
        }
        if (form.get(oppositeField).value) {
          form.get(oppositeField).setErrors(null)
        }
      })
    }
    return of(err)
  }
}
